import { model, schema } from '@/ts/base';
import {
  FieldModel,
  NodeCodeRule,
  NodeValidateRule,
  ValidateErrorInfo,
} from '@/ts/base/model';
import CodeRuleNode from '../graph/node/CodeRuleNode';
import ValidateRuleNode from '../graph/node/ValidateRuleNode';
import { FormChangeEvent } from '../types/rule';
import EventEmitter, { EventListener } from '../util/EventEmitter';
import FormServiceBase from './FormServiceBase';
import { IBelong } from '@/ts/core';
import { DetailOperationType, FormType } from '../types/service';
import { mixins } from '@/ts/base/common/lang/mixin';
import { mapErrorToValidateInfo } from '../../js/util';
import { IReportReception } from '@/ts/core/work/assign/reception/report';
import message from '@/utils/message';

export interface FormEventMap {
  /** 内部（计算和汇总）触发的表单值变更 */
  // valueChange: EventListener<FormChangeEvent>;
  /** 渲染规则值变更 */
  // renderChange: EventListener<RenderRule>;
  /** 计算完成的事件 */
  afterCalculate: EventListener<FormChangeEvent[]>;
  /** 批量更新事件 */
  batchUpdate: EventListener<FormChangeEvent[]>;
  /** 校验完成的事件 */
  afterValidate: EventListener<ValidateErrorInfo[]>;
}

export default class WorkFormService extends mixins(
  FormServiceBase,
  EventEmitter<FormEventMap>,
) {
  static createStandalone(
    belong: IBelong,
    form: schema.XForm,
    fields?: FieldModel[],
    allowEdit = false,
    data?: Dictionary<model.FormEditData[]>,
  ): WorkFormService {
    const model: model.InstanceDataModel = {
      node: {
        primaryForms: [form],
        detailForms: [],
        formRules: [],
        executors: [],
        id: 'nodeId',
      } as any,
      fields: {
        [form.id]: fields || [],
      },
      data: data ?? {},
      primary: {},
      rules: [],
    };
    const ret = new WorkFormService(belong, model, allowEdit);
    ret.init();
    return ret;
  }

  protected autoCalculate = true;

  dispose() {
    super.dispose();
    this.clearListeners();
  }

  /**
   * 全部计算
   * @returns 变更对象
   */
  async calculateAll() {
    let changes = await this.executable.calculateAll();
    this.dispatchEvent('afterCalculate', changes);
    return changes;
  }

  /**
   * 手动执行代码规则
   * @param rule 代码规则
   * @returns 脚本的返回值
   */
  async executeCodeRule(rule: NodeCodeRule) {
    const node = new CodeRuleNode(rule);
    let [_, changes] = await this.executable.calculateNode(node);
    this.dispatchEvent('batchUpdate', changes);
    for (const item of changes) {
      this.command.emitter('change', 'result', item);
    }
    return changes;
  }

  /**
   * 全部校验
   * @returns 校验结果
   */
  validateAll() {
    let errors: ValidateErrorInfo[] = this.validate.validateAll();
    this.handlingResult(errors);
    return errors;
  }

  handlingResult(result: ValidateErrorInfo[]) {
    const fieldMap = (result as model.RequiredValidateError[])
      .filter((err) => err.formId && err.field)
      .map((item) => item.field.id);
    for (let key in this.formInfo) {
      let formId = this.formInfo[key].form.id;
      this.formInfo[key].form.attributes.forEach((attribute) => {
        this.command.emitter('valid', 'isRequired', {
          formId: formId,
          destId: attribute.id,
          value: !fieldMap.includes(attribute.id),
        });
      });
    }
    // this.command.emitter('validate', 'errors', result);
    this.dispatchEvent('afterValidate', result);
  }

  /**
   * 主子表赋值规则
   * @returns
   */
  async assignment() {
    // 赋值规则
    const assignmentRule = this.model.node.formRules.find((i) => i.type === 'assignment');
    if (assignmentRule) {
      const detailFormId = assignmentRule.assignments[0].detail.formId;
      const detailDatas = this.formData.detail[detailFormId];
      for (const key in detailDatas) {
        if (Object.prototype.hasOwnProperty.call(detailDatas, key)) {
          const curDetail = detailDatas[key];
          for (let i = 0; i < assignmentRule.assignments.length; i++) {
            const assignment = assignmentRule.assignments[i];
            const primaryFormData =
              this.formData.primary[assignment.primary.formId][assignment.primary.id];
            curDetail[assignment.detail.id] = primaryFormData;
          }
        }
      }

      /** 通知变更子表更新 */
      this.command.emitter('change', 'assignment', {
        formId: detailFormId,
        data: Object.values(this.formData.detail[detailFormId]),
      });
      return true;
    }
  }

  /**
   * 资产合并计算
   */
  async assetMerge() {
    // 合并规则
    const mergeRule = this.model.node.formRules.find(
      (i) => i.type === 'combination' && i.applyType === '合并',
    );
    if (mergeRule) {
      // 合并的目标资产
      const mergePrimaryAsset =
        this.formData.primary[mergeRule.combination!.assetSource.formId][
          mergeRule.combination!.assetSource.id
        ];
      if (mergePrimaryAsset) {
        // 合并的目标资产的id
        const mergePrimaryAssetId = JSON.parse(mergePrimaryAsset)[0].id;
        // 核销表标记
        const verificationFormId = mergeRule.combination!.verificationFormId;
        // 变更表标记
        const detailFormId = this.model.node.detailForms.find(
          (i) => i.id !== verificationFormId,
        )?.id;
        if (!verificationFormId) {
          message.error('请添加核销子表');
          return null;
        }
        if (detailFormId) {
          const detailFormDatas = this.formData.detail[detailFormId];
          const fields = this.loadDetailCombinationFields(detailFormId);
          // 合并的目标资产
          const targetAsset = detailFormDatas[mergePrimaryAssetId];
          const newThing: schema.XThing[] = [];
          // 合并计算相加
          for (const key in detailFormDatas) {
            if (Object.prototype.hasOwnProperty.call(detailFormDatas, key)) {
              const element = detailFormDatas[key];
              if (element.id !== targetAsset.id) {
                for (let i = 0; i < fields.length; i++) {
                  const curentFields = fields[i];
                  if (['数值型', '货币型'].includes(curentFields.valueType)) {
                    targetAsset[curentFields.id] = targetAsset[curentFields.id] +=
                      element[curentFields.id];
                  }
                }
                newThing.push(element);
                /** 通知变更子表更新 */
                this.command.emitter('change', 'combination', {
                  formId: detailFormId,
                  data: [targetAsset],
                });
                /** 通知核销子表更新 */
                this.command.emitter('change', 'combination', {
                  formId: verificationFormId,
                  data: newThing,
                });
              }
            }
          }
        } else {
          message.error('请完善合并规则');
          return null;
        }
      } else {
        message.error('合并目标资产为空');
        return null;
      }
    } else {
      message.warn('请完善合并规则');
      return null;
    }
  }

  /** 加载子表受组合办事影响的字段 */
  loadDetailCombinationFields(detailFormId: string) {
    // 当前子表的字段值
    const allDetailCombinationFields = this.model.fields[detailFormId];
    // 当前子表的受组合办事影响的字段
    const detailFields = allDetailCombinationFields.filter(
      (item) => item.isCombination === true,
    );
    return detailFields;
  }

  /**
   * 手动执行校验规则
   * @param rule 校验规则
   * @returns 错误信息
   */
  async executeValidateRule(rule: NodeValidateRule) {
    const node = new ValidateRuleNode(rule);
    return this.validate.validateNode(node);
  }

  /**
   * 全部汇总
   * @param svc 报表实例
   * @returns 变更对象
   */
  async summaryAll(report: IReportReception) {
    const sum = await report.summaryAll();
    const changes: FormChangeEvent[] = [];
    for (const [formId, data] of Object.entries(sum)) {
      const form = this.formInfo[formId]?.form;
      if (!form) {
        console.warn(`找不到表单 ${formId}，汇总数据将被忽略`);
        continue;
      }

      const attrs = form.attributes.map((a) => a.id);
      for (const [attrId, value] of Object.entries(data)) {
        if (value == null) {
          continue;
        }

        if (!attrs.includes(attrId)) {
          continue;
        }

        changes.push({
          formId,
          destId: attrId,
          value,
        });
      }
    }

    this.dispatchEvent('batchUpdate', changes);
    return changes;
  }

  async runRuleEffects(formType: FormType, formId: string, field: string) {
    if (this.allowEdit && !this.hasReport) {
      try {
        for (const item of await this.calculateAll()) {
          this.command.emitter('change', 'result', item);
        }
      } catch (error) {
        console.error(error);
        this.handlingResult([
          mapErrorToValidateInfo(
            error,
            formType + ' ' + (this.formInfo[formId]?.form.name ?? formId) + ' ' + field,
          ),
        ]);
      }
    }

    if (formType === '主表') {
      if (this.formInfo[formId].form.options?.businessType === '拆分') {
        this.split.runWorkSplitRules(formId, field);
      }
      const changes = this.render.runWorkRules(formId, field, (params) => {
        return params.formId
          ? this.model.data[params.formId].at(-1)?.after[0][field]
          : undefined;
      });
      for (const rule of changes) {
        this.model.rules = this.model.rules.filter((item) => {
          return item.formId != rule.formId && item.destId != rule.destId;
        });
        this.model.rules.push(rule);
        this.command.emitter('change', rule.typeName, rule);
      }
    }
  }

  async onPrimaryFieldChanged(formId: string, field: string) {
    const data = this.model.data[formId].at(-1)?.after[0];
    if (data) {
      this.updatePrimaryData(formId, data);
    }

    this.runRuleEffects('主表', formId, field);
  }

  async onDetailDataChanged(
    formId: string,
    type: DetailOperationType | 'all',
    rows: schema.XThing[],
  ) {
    switch (type) {
      case 'add':
      case 'update':
        for (const row of rows) {
          this.formData.detail[formId][row.id] = row;
        }
      case 'remove':
        for (const row of rows) {
          delete this.formData.detail[formId][row.id];
        }
      case 'all':
        this.updateDetailData(formId, rows);
        break;
      default:
        break;
    }

    this.runRuleEffects('子表', formId, type);
  }
}
